#include "wm_crypto_hard.h"

/**************** random *****************/

/**
 * @brief          	This function is used to generate true random number.
 *
 * @param[out]   	out 			Pointer to the output of random number.
 * @param[in]   	len 			The random number length.
 *
 * @retval  		HAL_OK  		success
 * @retval  		HAL_ERROR   	failed
 *
 * @note           	None
 */
HAL_StatusTypeDef HAL_CRYPTO_TRNG(uint8_t *out, uint32_t len)
{
	CRYPTO_TypeDef *Instance = CRYPTO;
	uint32_t randomBytes = 4, val;
	uint32_t clkreg;
	
	if ((out == NULL) || (len == 0))
	{
		return HAL_ERROR;
	}
	
	clkreg = RCC->CLK_EN;
	if ((clkreg & RCC_CLK_EN_TOUCH) == 0)
	{
		__HAL_RCC_TOUCH_CLK_ENABLE();
	}
	__HAL_RCC_GPSEC_CLK_ENABLE();
	Instance->TRNG_CR = CRYPTO_TRNG_CR_IM | (4 << CRYPTO_TRNG_CR_CP_Pos) | CRYPTO_TRNG_CR_OUT_SEL | CRYPTO_TRNG_CR_EN;
	CLEAR_BIT(Instance->TRNG_CR, CRYPTO_TRNG_CR_IM);
	HAL_Delay(1);
	while (len > 0)
	{
		while(READ_BIT(Instance->GPSEC_SR, CRYPTO_GPSEC_SR_IF) == 0)
		{
			
		}
		SET_BIT(Instance->GPSEC_SR, 0x10000);
		val = Instance->RNG_RESULT;
		if (len >= randomBytes)
		{
			memcpy(out, (char *)&val, randomBytes);
			out += randomBytes;
			len -= randomBytes;
		}
		else
		{
			memcpy(out, (char *)&val, len);
			len = 0;
		}
	}
	Instance->TRNG_CR = 0x40;
	SET_BIT(Instance->GPSEC_SR, 0x10000);
	__HAL_RCC_GPSEC_CLK_DISABLE();
	if ((clkreg & RCC_CLK_EN_TOUCH) == 0)
	{
		__HAL_RCC_TOUCH_CLK_DISABLE();
	}
	
	return HAL_OK;
}

/**************** CRC *****************/

HAL_StatusTypeDef HAL_CRYPTO_CRC_Init(CRC_HandleTypeDef *hcrytpo_crc, uint32_t key, uint32_t type, uint32_t mode)
{
	if (hcrytpo_crc == NULL)
	{
		return HAL_ERROR;
	}
	hcrytpo_crc->Instance = CRYPTO;
	hcrytpo_crc->Init.value = key;
	hcrytpo_crc->Init.type = type;
	hcrytpo_crc->Init.mode = mode;
	
	return HAL_OK;
}

static uint32_t Reflect(uint32_t ref, uint8_t ch)
{
	int i;
	uint32_t value = 0;
	
	for (i = 1; i < (ch + 1); i++)
	{
		if (ref & 1)
		{
			value |= 1 << (ch - i);
		}
		ref >>= 1;
	}
	return value;
}

HAL_StatusTypeDef HAL_CRYPTO_CRC_Update(CRC_HandleTypeDef *hcrypto_crc, uint8_t *in, uint32_t len)
{
	uint32_t sec_cfg;
	
	__HAL_RCC_GPSEC_CLK_ENABLE();
	sec_cfg = CRYPTO_GPSEC_CFG_CYPHER_MODE_CRC | hcrypto_crc->Init.type | 
					hcrypto_crc->Init.mode | CRYPTO_GPSEC_CFG_IM | (len & CRYPTO_GPSEC_CFG_LEN);
	WRITE_REG(hcrypto_crc->Instance->GPSEC_CFG, sec_cfg);
	if(hcrypto_crc->Init.mode & OUTPUT_REFLECT)
	{
		uint8_t ch_crc = 16;
		uint32_t value = 0;
		switch(hcrypto_crc->Init.type)
		{
			case CRYPTO_CRC_TYPE_8:
				ch_crc = 8;
				break;
			case CRYPTO_CRC_TYPE_16_MODBUS:
				ch_crc = 16;
				break;
			case CRYPTO_CRC_TYPE_16_CCITT:
				ch_crc = 16;
				break;
			case CRYPTO_CRC_TYPE_32:
				ch_crc = 32;
				break;
			default:
				break;
		}
		value = Reflect(hcrypto_crc->Init.value, ch_crc);
		WRITE_REG(hcrypto_crc->Instance->SHA1_DIGEST4, value);
	}
	else
	{
		WRITE_REG(hcrypto_crc->Instance->SHA1_DIGEST4, hcrypto_crc->Init.value);
	}
	
	WRITE_REG(hcrypto_crc->Instance->SRC_ADDR, (uint32_t)in);
	WRITE_REG(hcrypto_crc->Instance->GPSEC_CTRL, CRYPTO_GPSEC_CTRL_START);
	while(READ_BIT(hcrypto_crc->Instance->GPSEC_SR, CRYPTO_GPSEC_SR_IF) == 0)
	{
		
	}
	SET_BIT(hcrypto_crc->Instance->GPSEC_SR, 0x10000);
	hcrypto_crc->Init.value = READ_REG(hcrypto_crc->Instance->SHA1_DIGEST4);
	WRITE_REG(hcrypto_crc->Instance->GPSEC_CTRL, 0x4);
	__HAL_RCC_GPSEC_CLK_DISABLE();
	
	return HAL_OK;
}

HAL_StatusTypeDef HAL_CRYPTO_CRC_Final(CRC_HandleTypeDef *hcrypto_crc, uint32_t *out)
{
	*out = hcrypto_crc->Init.value;
	return HAL_OK;
}

/**************** AES DES 3DES RC4 *****************/


static void CryptoSetKey(psCipherContext_t *ctx, void *key, uint32_t keylen)
{
	uint32_t *key32 = (uint32_t *)key;
	int i = 0;
	uint32_t *p = (uint32_t *)&(ctx->Instance->KEY00);
	
	for (i = 0; (i < (keylen / 4)) && (i < 6); i++)
	{
		M32(p + i) = key32[i];
	}
	if (keylen >= 28)
	{
		ctx->Instance->KEY30 = key32[6];
	}
	if (keylen == 32)
	{
		ctx->Instance->KEY31 = key32[7];
	}
}

static void CryptoSetIv(psCipherContext_t *ctx, void *iv, int ivlen)
{
	uint32_t *IV32 = (uint32_t *)iv;
	
	if (ivlen >= 8)
	{
		ctx->Instance->IV00 = IV32[0];
		ctx->Instance->IV01 = IV32[1];
	}
	if (ivlen == 16)
	{
		ctx->Instance->KEY20 = IV32[2];
		ctx->Instance->KEY21 = IV32[3];
	}
}

/**
 * @brief          	This function initializes a AES encryption algorithm,  i.e. fills the psCipherContext_t structure pointed to by ctx with necessary data.
 *
 * @param[in]   	ctx 		Pointer to the Cipher Context.
 * @param[in]   	IV 			Pointer to the Initialization Vector
 * @param[in]   	key 		Pointer to the key.
 * @param[in]		keylen 		the length of key.
 * @param[in]   	cbc 		the encryption mode, AES supports ECB/CBC/CTR modes.
 *
 * @retval  		HAL_OK  	success
 * @retval  		HAL_ERROR   failed
 *
 * @note             	None
 */
HAL_StatusTypeDef HAL_CRYPTO_AES_Init(psCipherContext_t *ctx, const uint8_t *IV, const uint8_t *key, uint32_t keylen, CRYPTO_MODE cbc)
{
	int x = 0;
	
	if (keylen != 16)
	{
		return HAL_ERROR;
	}
	ctx->Instance = CRYPTO;
	memcpy(ctx->u.aes.key.skey, key, keylen);
	ctx->u.aes.key.type = cbc;
	ctx->u.aes.key.rounds = 16;
	if (IV)
	{
		for (x = 0; x < ctx->u.aes.key.rounds; x++)
		{
			ctx->u.aes.IV[x] = IV[x];
		}
	}
	
	return HAL_OK;
}

/**
 * @brief			This function encrypts or decrypts a variable length data stream according to AES.
 *
 * @param[in]		ctx 		Pointer to the Cipher Context.
 * @param[in]		in 			Pointer to the input plaintext data stream(or the encrypted text data stream) of variable length.
 * @param[in]		out 		Pointer to the resulting ciphertext data stream.
 * @param[in]		len 		Length of the plaintext data stream in octets.
 * @param[in]		way 		The cryption way which indicates encryption or decryption.
 *
 * @retval			HAL_OK  	success
 * @retval			HAL_ERROR	failed
 *
 * @note			None
 */
HAL_StatusTypeDef HAL_CRYPTO_AES_Encrypt_Decrypt(psCipherContext_t *ctx, uint8_t *in, uint8_t *out, uint32_t len, CRYPTO_WAY way)
{
	uint32_t keylen = 16;
	uint8_t *key = (uint8_t *)ctx->u.aes.key.skey;
	uint8_t *IV = ctx->u.aes.IV;
	CRYPTO_MODE cbc = (CRYPTO_MODE)(ctx->u.aes.key.type & 0xFF);
	
	__HAL_RCC_GPSEC_CLK_ENABLE();
	CryptoSetKey(ctx, key, keylen);
	CryptoSetIv(ctx, IV, 16);
	
	ctx->Instance->SRC_ADDR = (uint32_t)in;
	ctx->Instance->DEST_ADDR = (uint32_t)out;
	ctx->Instance->GPSEC_CFG = CRYPTO_GPSEC_CFG_CYPHER_MODE_AES | CRYPTO_GPSEC_CFG_AES_RST | (way << CRYPTO_GPSEC_CFG_MODE_Pos) | 
						(cbc << CRYPTO_GPSEC_CFG_SUB_MODE_Pos) | (len & 0xFFFF);
	
	ctx->Instance->GPSEC_CTRL = CRYPTO_GPSEC_CTRL_START;
	while(READ_BIT(ctx->Instance->GPSEC_SR, CRYPTO_GPSEC_SR_IF) == 0)
	{
		
	}
	SET_BIT(ctx->Instance->GPSEC_SR, 0x10000);
	__HAL_RCC_GPSEC_CLK_DISABLE();
	
	return HAL_OK;
}

/**
 * @brief          	This function initializes a RC4 encryption algorithm,
 *					i.e. fills the psCipherContext_t structure pointed to by ctx with necessary data.
 *
 * @param[in]   	ctx 		Pointer to the Cipher Context.
 * @param[in]   	key 		Pointer to the key.
 * @param[in]   	keylen 		the length of key.
 *
 * @retval  		HAL_OK  	success
 * @retval  		HAL_ERROR   failed

 *
 * @note            The first parameter ctx must be a structure which is allocated externally.
 *      			And all of Context parameters in the initializing methods should be allocated externally too.
 */
HAL_StatusTypeDef HAL_CRYPTO_RC4_Init(psCipherContext_t *ctx, uint8_t *key, uint32_t keylen)
{
	if (((keylen != 16) && (keylen != 32)) || (key == NULL) || (ctx == NULL))
	{
		return HAL_ERROR;
	}
	ctx->Instance = CRYPTO;
	memcpy(ctx->u.arc4.state, key, keylen);
	ctx->u.arc4.byteCount = keylen;
	
	return HAL_OK;
}

/**
 * @brief          	This function encrypts a variable length data stream according to RC4.
 *					The RC4 algorithm it generates a "keystream" which is simply XORed with the plaintext to produce the ciphertext stream.
 *					Decryption is exactly the same as encryption. This function also decrypts a variable length data stream according to RC4.
 *
 * @param[in]   	ctx 		Pointer to the Cipher Context.
 * @param[in]   	in 			Pointer to the input plaintext data stream(or the encrypted text data stream) of variable length.
 * @param[in]   	out 		Pointer to the resulting ciphertext data stream.
 * @param[in]		len 		Length of the plaintext data stream in octets.
 *
 * @retval  		HAL_OK  	success
 * @retval  		HAL_ERROR   failed
 *
 * @note            None
 */
HAL_StatusTypeDef HAL_CRYPTO_RC4(psCipherContext_t *ctx, uint8_t *in, uint8_t *out, uint32_t len)
{
	uint8_t *key = ctx->u.arc4.state;
	uint32_t temp, keylen = ctx->u.arc4.byteCount;
	
	__HAL_RCC_GPSEC_CLK_ENABLE();
	CryptoSetKey(ctx, key, keylen);
	ctx->Instance->SRC_ADDR = (uint32_t)in;
	ctx->Instance->DEST_ADDR = (uint32_t)out;
	temp = CRYPTO_GPSEC_CFG_CYPHER_MODE_RC4 | CRYPTO_GPSEC_CFG_RC4_RST | (len & 0xFFFF);
	if (keylen == 32)
	{
		temp |= CRYPTO_GPSEC_CFG_RC4_256;
	}
	ctx->Instance->GPSEC_CFG = temp;
	ctx->Instance->GPSEC_CTRL = CRYPTO_GPSEC_CTRL_START;
	while(READ_BIT(ctx->Instance->GPSEC_SR, CRYPTO_GPSEC_SR_IF) == 0)
	{
		
	}
	SET_BIT(ctx->Instance->GPSEC_SR, 0x10000);
	__HAL_RCC_GPSEC_CLK_DISABLE();
	
	return HAL_OK;
}

/**
 * @brief			This function initializes a 3DES encryption algorithm,  i.e. fills the psCipherContext_t structure pointed to by ctx with necessary data.
 *
 * @param[in]		ctx 		Pointer to the Cipher Context.
 * @param[in]		IV 			Pointer to the Initialization Vector
 * @param[in]		key 		Pointer to the key.
 * @param[in]		keylen 		The length of key.
 * @param[in]		cbc 		The encryption mode, 3DES supports ECB/CBC modes.
 *
 * @retval			HAL_OK  	success
 * @retval			HAL_ERROR	failed
 *
 * @note			None
 */
HAL_StatusTypeDef HAL_CRYPTO_3DES_Init(psCipherContext_t *ctx, uint8_t *IV, uint8_t *key, uint32_t keylen, CRYPTO_MODE cbc)
{
	int i = 0;
	
	if (keylen != DES3_KEY_LEN)
	{
		return HAL_ERROR;
	}
	ctx->Instance = CRYPTO;
	memcpy(ctx->u.des3.key.ek[0], key, keylen);
	ctx->u.des3.key.ek[1][0] = cbc;
	ctx->u.des3.blocklen = DES3_IV_LEN;
	if (IV)
	{
		for (i = 0; i < ctx->u.des3.blocklen; i++)
		{
			ctx->u.des3.IV[i] = IV[i];
		}
	}
	return HAL_OK;
}

/**
 * @brief			This function encrypts or decrypts a variable length data stream according to 3DES.
 *
 * @param[in]		ctx 		Pointer to the Cipher Context.
 * @param[in]		in 			Pointer to the input plaintext data stream(or the encrypted text data stream) of variable length.
 * @param[in]		out 		Pointer to the resulting ciphertext data stream.
 * @param[in]		len 		Length of the plaintext data stream in octets.
 * @param[in]		way 		The cryption way which indicates encryption or decryption.
 *
 * @retval			HAL_OK  	success
 * @retval			HAL_ERROR	failed
 *
 * @note			None
 */
HAL_StatusTypeDef HAL_CRYPTO_3DES_Encrypt_Decrypt(psCipherContext_t *ctx, uint8_t *in, uint8_t *out, uint32_t len, CRYPTO_WAY way)
{
	uint32_t keylen = DES3_KEY_LEN;
	uint8_t *key = (uint8_t *)ctx->u.des3.key.ek[0];
	uint8_t *IV = ctx->u.des3.IV;
	CRYPTO_MODE cbc = (CRYPTO_MODE)(ctx->u.des3.key.ek[1][0] & 0xFF);
	
	__HAL_RCC_GPSEC_CLK_ENABLE();
	CryptoSetKey(ctx, key, keylen);
	CryptoSetIv(ctx, IV, DES3_IV_LEN);
	ctx->Instance->SRC_ADDR = (uint32_t)in;
	ctx->Instance->DEST_ADDR = (uint32_t)out;
	ctx->Instance->GPSEC_CFG = CRYPTO_GPSEC_CFG_CYPHER_MODE_3DES | CRYPTO_GPSEC_CFG_DES_RST | (way << CRYPTO_GPSEC_CFG_MODE_Pos) | 
								(cbc << CRYPTO_GPSEC_CFG_SUB_MODE_Pos) | (len & 0xFFFF);
	ctx->Instance->GPSEC_CTRL = CRYPTO_GPSEC_CTRL_START;
	while(READ_BIT(ctx->Instance->GPSEC_SR, CRYPTO_GPSEC_SR_IF) == 0)
	{
		
	}
	SET_BIT(ctx->Instance->GPSEC_SR, 0x10000);
	__HAL_RCC_GPSEC_CLK_DISABLE();
	
	return HAL_OK;
}

/**
 * @brief			This function initializes a DES encryption algorithm,  i.e. fills the psCipherContext_t structure pointed to by ctx with necessary data.
 *
 * @param[in]		ctx 		Pointer to the Cipher Context.
 * @param[in]		IV 			Pointer to the Initialization Vector
 * @param[in]		key 		Pointer to the key.
 * @param[in]		keylen 		the length of key.
 * @param[in]		cbc 		the encryption mode, DES supports ECB/CBC modes.
 *
 * @retval			HAL_OK  	success
 * @retval			HAL_ERROR	failed
 *
 * @note			None
 */
HAL_StatusTypeDef HAL_CRYPTO_DES_Init(psCipherContext_t *ctx, uint8_t *IV, uint8_t *key, uint32_t keylen, CRYPTO_MODE cbc)
{
	uint32_t i = 0;
	
	if (keylen != DES_KEY_LEN)
	{
		return HAL_ERROR;
	}
	ctx->Instance = CRYPTO;
	memcpy(ctx->u.des3.key.ek[0], key, keylen);
	ctx->u.des3.key.ek[1][0] = cbc;
	ctx->u.des3.blocklen = DES3_IV_LEN;
	if (IV)
	{
		for (i = 0; i < ctx->u.des3.blocklen; i++)
		{
			ctx->u.des3.IV[i] = IV[i];
		}
	}
	
	return HAL_OK;
}

/**
 * @brief			This function encrypts or decrypts a variable length data stream according to DES.
 *
 * @param[in]		ctx 		Pointer to the Cipher Context.
 * @param[in]		in 			Pointer to the input plaintext data stream(or the encrypted text data stream) of variable length.
 * @param[in]		out 		Pointer to the resulting ciphertext data stream.
 * @param[in]		len 		Length of the plaintext data stream in octets.
 * @param[in]		way 		The cryption way which indicates encryption or decryption.
 *
 * @retval			HAL_OK  	success
 * @retval			HAL_ERROR	failed
 *
 * @note			None
 */
HAL_StatusTypeDef HAL_CRYPTO_DES_Encrypt_Decrypt(psCipherContext_t *ctx, uint8_t *in, uint8_t *out, uint32_t len, CRYPTO_WAY way)
{
	uint32_t keylen = DES_KEY_LEN;
	uint8_t *key = (uint8_t *)ctx->u.des3.key.ek[0];
	uint8_t *IV = ctx->u.des3.IV;
	CRYPTO_MODE cbc = (CRYPTO_MODE)(ctx->u.des3.key.ek[1][0] & 0xFF);
	
	__HAL_RCC_GPSEC_CLK_ENABLE();
	CryptoSetKey(ctx, key, keylen);
	CryptoSetIv(ctx, IV, DES3_IV_LEN);
	ctx->Instance->SRC_ADDR = (uint32_t)in;
	ctx->Instance->DEST_ADDR = (uint32_t)out;
	ctx->Instance->GPSEC_CFG = CRYPTO_GPSEC_CFG_CYPHER_MODE_DES | CRYPTO_GPSEC_CFG_DES_RST | (way << CRYPTO_GPSEC_CFG_MODE_Pos) | 
								(cbc << CRYPTO_GPSEC_CFG_SUB_MODE_Pos) | (len & 0xFFFF);
	ctx->Instance->GPSEC_CTRL = CRYPTO_GPSEC_CTRL_START;
	while(READ_BIT(ctx->Instance->GPSEC_SR, CRYPTO_GPSEC_SR_IF) == 0)
	{
		
	}
	SET_BIT(ctx->Instance->GPSEC_SR, 0x10000);
	__HAL_RCC_GPSEC_CLK_DISABLE();
	
	return HAL_OK;
}